package com.leyou.search.service.impl;

import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.IndexService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author YYP
 * @date 2021/1/5
 */
@Slf4j
@Service
public class IndexServiceImpl implements IndexService {
    private final GoodsRepository goodsRepository;

    private final ItemClient itemClient;

    public IndexServiceImpl(GoodsRepository goodsRepository, ItemClient itemClient) {
        this.goodsRepository = goodsRepository;
        this.itemClient = itemClient;
    }


    @Override
    public void createIndexAndMapping() {
        try {
            this.goodsRepository.deleteIndex();
            log.info("删除索引库成功");
        } catch (Exception e) {
            log.info("索引库不存在");
            e.printStackTrace();
        }

        //创建索引库
        this.goodsRepository.createIndex("{\n" +
                "  \"settings\": {\n" +
                "    \"analysis\": {\n" +
                "      \"analyzer\": {\n" +
                "        \"my_pinyin\": {\n" +
                "          \"tokenizer\": \"ik_smart\",\n" +
                "          \"filter\": [\n" +
                "            \"py\"\n" +
                "          ]\n" +
                "        }\n" +
                "      },\n" +
                "      \"filter\": {\n" +
                "        \"py\": {\n" +
                "\t\t  \"type\": \"pinyin\",\n" +
                "          \"keep_full_pinyin\": true,\n" +
                "          \"keep_joined_full_pinyin\": true,\n" +
                "          \"keep_original\": true,\n" +
                "          \"limit_first_letter_length\": 16,\n" +
                "          \"remove_duplicated_term\": true\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  },\n" +
                "  \"mappings\": {\n" +
                "    \"properties\": {\n" +
                "      \"id\": {\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"suggestion\": {\n" +
                "        \"type\": \"completion\",\n" +
                "        \"analyzer\": \"my_pinyin\",\n" +
                "        \"search_analyzer\": \"ik_smart\"\n" +
                "      },\n" +
                "      \"title\":{\n" +
                "        \"type\": \"text\",\n" +
                "        \"analyzer\": \"my_pinyin\",\n" +
                "        \"search_analyzer\": \"ik_smart\"\n" +
                "      },\n" +
                "      \"image\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"index\": false\n" +
                "      },\n" +
                "      \"updateTime\":{\n" +
                "        \"type\": \"date\"\n" +
                "      },\n" +
                "      \"specs\":{\n" +
                "        \"type\": \"nested\",\n" +
                "        \"properties\": {\n" +
                "          \"name\":{\"type\": \"keyword\" },\n" +
                "          \"value\":{\"type\": \"keyword\" }\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  }\n" +
                "}");
        log.info("索引库创建成功");
    }

    //1.从item服务查询spu数据，所有
    @Override
    public void loadData() {
        long page = 1, rows = 100;
        while (true) {
            //查询全部
            PageDTO<SpuDTO> result = itemClient.findSpuByPage(page, rows, null, null, null, true);

            //如果没有数据
            if (result.getTotalPage() == 0) {
                break;
            }


            //有数据要被获取,把获取到的spuDTO的集合转换为goods集合
            List<Goods> goodsList = result.getItems()
                    .stream()
                    .map(this::buildGoods)
                    .collect(Collectors.toList());

            //保存到索引库
            this.goodsRepository.saveAll(goodsList);
            page++;
            //当前页大于总页数时，停止
            if (page > result.getTotalPage()) {
                break;
            }
        }

    }

    @Override
    public void deleteById(Long id) {
        //根据id删除数据
        this.goodsRepository.deleteById(id);
    }

    @Override
    public void createIndexById(Long id) {
        //spuDT0->Goods
        //根据spuId查询对应的spu信息
        PageDTO<SpuDTO> spuDTOPageDTO = this.itemClient.findSpuByPage(1L, 1L, null, null, id, true);

        //查到商品上架处理
        if (spuDTOPageDTO.getTotalPage() == 1) {
            SpuDTO spuDTO = spuDTOPageDTO.getItems().get(0);
            Goods goods = buildGoods(spuDTO);

            this.goodsRepository.save(goods);
        }

    }

    //2.把查询到的数据转换为goods，（先创建索引库）然后保存到索引库
    @Override
    public Goods buildGoods(SpuDTO spuDTO) {
        Goods goods = new Goods();
        //相同名称以及类型的属性可以直接对拷，title除外
        BeanUtils.copyProperties(spuDTO, goods);


        BrandDTO brandDTO = itemClient.queryBrandById(spuDTO.getBrandId());
        //取出品牌名称后面括号的内容
        String brandName = StringUtils.substringBefore(brandDTO.getName(), "（");
        CategoryDTO categoryDTO = itemClient.queryCategoryById(spuDTO.getCid3());
        String categoryName = categoryDTO.getName();

        //title
        String title = null;
        //goods中的title属性要包含，分类，以及品牌的信息，这样没有直接写商品名称，写了分类或者品牌名称也能搜索到当前商品
        title = spuDTO.getName() +(spuDTO.getName().contains(categoryName) ? "":categoryName)
        + (spuDTO.getName().contains(brandName) ? "": brandName);

        goods.setTitle(title);

        //suggestion
        //自动补全候选内容，分别包含，品牌，分类，品牌+分类，分类+品牌，商品名称，品牌+商品名称
        ArrayList<String> suggs = new ArrayList<>();
        suggs.add(brandName);
        suggs.add(categoryName);
        suggs.add(brandName + categoryName);
        suggs.add(categoryName + brandName);
        suggs.add(spuDTO.getName());
        suggs.add(brandName + spuDTO.getName());

        goods.setSuggestion(suggs);

        //categoryId
        goods.setCategoryId(spuDTO.getCid3());

        //images
        List<SkuDTO> skuDTOList = this.itemClient.listSkuBySpuId(spuDTO.getId());
        goods.setImage(StringUtils.substringBefore(skuDTOList.get(0).getImages(),","));

        //specs，所有可搜索过滤规格参数名称以及对应的值
        ArrayList<Map<String, Object>> specsResult = new ArrayList<>();
        //获取所有的可搜索规格参数信息的属性名称以及对应的值，
        List<SpecParamDTO> specParamDTOS = this.itemClient.listSpuParamsByQuery(spuDTO.getId(), true);

        for (SpecParamDTO specParamDTO : specParamDTOS) {
            Map<String,Object> result = new HashMap<>();
            //name，规格参数的名称，value，规格参数的值
            result.put("name",specParamDTO.getName());
            //保存值应该给可以进行区间处理的值，划分区间
            result.put("value",chooseSegment(specParamDTO));

            specsResult.add(result);
        }
        goods.setSpecs(specsResult);


        HashSet<Long> price = new HashSet<>();
        long sold = 0;
        for (SkuDTO skuDTO : skuDTOList) {
            price.add(skuDTO.getPrice());
            sold = sold + skuDTO.getSold();
        }

        //prices
        goods.setPrices(price);

        //sold
        goods.setSold(sold);

        //updateTime
        goods.setUpdateTime(new Date());

        return goods;
    }



/*    @Override
    public void createIndexAndMapping() {
        // 删除已经存在的索引库
        try {
            goodsRepository.deleteIndex();
        } catch (Exception e) {
            log.error("删除失败，可能索引库不存在！", e);
        }

        // 然后创建一个新的
        goodsRepository.createIndex("{\n" +
                "  \"settings\": {\n" +
                "    \"analysis\": {\n" +
                "      \"analyzer\": {\n" +
                "        \"my_pinyin\": {\n" +
                "          \"tokenizer\": \"ik_smart\",\n" +
                "          \"filter\": [\n" +
                "            \"py\"\n" +
                "          ]\n" +
                "        }\n" +
                "      },\n" +
                "      \"filter\": {\n" +
                "        \"py\": {\n" +
                "\t\t  \"type\": \"pinyin\",\n" +
                "          \"keep_full_pinyin\": true,\n" +
                "          \"keep_joined_full_pinyin\": true,\n" +
                "          \"keep_original\": true,\n" +
                "          \"limit_first_letter_length\": 16,\n" +
                "          \"remove_duplicated_term\": true\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  },\n" +
                "  \"mappings\": {\n" +
                "    \"properties\": {\n" +
                "      \"id\": {\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"suggestion\": {\n" +
                "        \"type\": \"completion\",\n" +
                "        \"analyzer\": \"my_pinyin\",\n" +
                "        \"search_analyzer\": \"ik_smart\"\n" +
                "      },\n" +
                "      \"title\":{\n" +
                "        \"type\": \"text\",\n" +
                "        \"analyzer\": \"my_pinyin\",\n" +
                "        \"search_analyzer\": \"ik_smart\"\n" +
                "      },\n" +
                "      \"image\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"index\": false\n" +
                "      },\n" +
                "      \"updateTime\":{\n" +
                "        \"type\": \"date\"\n" +
                "      },\n" +
                "      \"specs\":{\n" +
                "        \"type\": \"nested\",\n" +
                "        \"properties\": {\n" +
                "          \"name\":{\"type\": \"keyword\" },\n" +
                "          \"value\":{\"type\": \"keyword\" }\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  }\n" +
                "}");
    }

    //1.从item服务查询spu数据，所有
    @Override
    public void loadData() {
        int page =1, rows = 100;
        while (true) {
            log.info("开始导入第{}页数据",page);
            //分页查询已经上架的spu
            PageDTO<SpuDTO> result = itemClient.querySpuByPage(page, rows, true, null, null, null);

            //第一页就没有数据
            if (result.getTotalPage() == 0) {
                break;
            }

            //有数据就查询
            List<SpuDTO> list = result.getItems();
            //遍历spu集合，吧spudto通过buildGoods方法转为Goods
            List<Goods> goodsList = list.stream().map(this::buildGoods).collect(Collectors.toList());

            //批量写入Elesticsearch
            goodsRepository.saveAll(goodsList);
            log.info("导入第{}页数据结束",page);
            //翻页
            page++;

            //获取总页数
            Long totalPage = result.getTotalPage();
            // 判断是否还有spu没有查询，当下一次要查询的页码比总页码多，就停止
            if (page > totalPage) {
                // 没有则结束
                break;
            }
        }
    }

    //2.把查询到的数据转换为goods，（先创建索引库）然后保存到索引库
    @Override
    public Goods buildGoods(SpuDTO spu) {
        // 1.自动补全的提示字段
        List<String> suggestion = new ArrayList<>(
                Arrays.asList(StringUtils.split(spu.getCategoryName(), "/")));
        suggestion.add(spu.getName());
        suggestion.add(spu.getBrandName());

        // 2.sku的价格集合
        // 2.1.查询sku集合
        List<SkuDTO> skuList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuList)) {
            // 没有sku，我们去查询
            skuList = itemClient.querySkuBySpuId(spu.getId());
        }
        // 2.2.获取价格集合
        Set<Long> prices = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 3.商品销量
        long sold = skuList.stream().mapToLong(SkuDTO::getSold).sum();

        // 4.sku的某个图片
        String image = StringUtils.substringBefore(skuList.get(0).getImages(), ",");

        // 5.规格参数
        List<Map<String, Object>> specs = new ArrayList<>();
        // 5.1.查询规格参数name和value键值对，只查询参与搜索的
        List<SpecParamDTO> params = itemClient.querySpecsValues(spu.getId(), true);
        // 5.2.封装
        for (SpecParamDTO param : params) {
            Map<String, Object> map = new HashMap<>(2);
            map.put("name", param.getName());
            map.put("value", chooseSegment(param));
            specs.add(map);
        }

        // 创建Goods对象，并封装数据
        Goods goods = new Goods();
        goods.setUpdateTime(new Date());
        // 自动补全的提示字段
        goods.setSuggestion(suggestion);
        // 规格参数
        goods.setSpecs(specs);
        // 商品销量
        goods.setSold(sold);
        // 商品标题
        goods.setTitle(spu.getTitle() + StringUtils.join(suggestion, " "));
        // sku的价格集合
        goods.setPrices(prices);
        // sku的某个图片
        goods.setImage(image);
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setId(spu.getId());
        return goods;
    }*/

    private Object chooseSegment(SpecParamDTO p) {
        Object value = p.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if (!p.getNumeric() || StringUtils.isBlank(p.getSegments()) || value instanceof Collection) {
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


}
